package org.activequant.container.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.activequant.tradesystem.annotation.Inject;

/**
 * 
 * helper class to inject into a class some other classes.<br>
 * <br>
 * <b>History:</b><br>
 * - [24.05.2008] Created (Ulrich Staudinger)<br>
 * 
 * @author Ulrich Staudinger
 */
public class Injector {
	/**
	 * injects/sets the given toBeInjected(Object) value into all fields in the given injectionTarget(Object) who are annotated with the <strong>Inject</strong> annotation
	 * @param injectionTarget
	 * @param toBeInjected
	 * @throws Exception
	 */
	// ############# reflection #############
	public void inject(Object injectionTarget, Object toBeInjected) throws Exception {
		// field injection
		for (Field field : getFieldsByAnnotation(injectionTarget.getClass(), Inject.class)) {
			// get object type
			Class<?> type = field.getType();
			// get object
			Inject annotation = field.getAnnotation(Inject.class);
			// String name = getAnnotationName(annotation);

			// set object
			if (toBeInjected != null) {
				if (!field.isAccessible())
					field.setAccessible(true);
				if (type.isAssignableFrom(toBeInjected.getClass()))
					field.set(injectionTarget, toBeInjected);
			} else if (!annotation.optional()) {
				throw new IllegalArgumentException("Non-optional object '" + type.getSimpleName() + "' is null (not found).");
			}
		}

	}

	// private String getAnnotationName(Inject annotation) {
	// return annotation.name().equals("") ? null : annotation.name();
	// }
	/**
	 * returns a Field[] array with the fields(including inherited ones) of the given clazz(Class&lt;?&gt;) which are annotated with the given annotation(Class&lt;? extends Annotation&gt;)
	 */
	private Field[] getFieldsByAnnotation(Class<?> clazz, Class<? extends Annotation> annotation) {
		List<Field> fields = new ArrayList<Field>();
		for (Field field : getFields(clazz)) {
			if (field.isAnnotationPresent(annotation)) {
				fields.add(field);
			}
		}
		return fields.toArray(new Field[] {});
	}

	/**
	 * returns a Field[] array with the fields(including inherited ones) of the given clazz(Class&lt;?&gt;)
	 * 
	 * @param clazz
	 * @return
	 */
	private Field[] getFields(Class<?> clazz) {
		List<Field> fields = new ArrayList<Field>();
		for (Class<?> parent : getSuperClasses(clazz)) {
			Field[] declaredFields = parent.getDeclaredFields();
			fields.addAll(Arrays.asList(declaredFields));
		}
		return fields.toArray(new Field[] {});
	}

	/**
	 * returns a Class&lt;?&gt;[] array holding the superclasses of the given
	 * clazz(Class&lt;?&gt;)
	 * 
	 * @param clazz
	 * @return
	 */
	private Class<?>[] getSuperClasses(Class<?> clazz) {
		List<Class<?>> classes = new ArrayList<Class<?>>();
		Class<?> main = clazz;
		while (main != null) {
			classes.add(main);
			main = main.getSuperclass();
		}
		return classes.toArray(new Class<?>[] {});
	}

}
